长列表优化之虚拟列表(项目)
长列表优化之虚拟列表
用Vue代码实现上面的问题
好的,以下是一个使用Vue实现虚拟列表的代码示例:
这个示例中,我们首先通过fetchData方法模拟异步获取列表数据,然后在mounted生命周期函数中获取数据,并将其保存到items数据属性中。在模板中,我们使用一个v-for指令来动态渲染当前可见的列表项,并使用visibleItems计算属性来计算当前可见的列表项。在handleScroll方法中,我们处理滚动事件,根据滚动条的位置计算出当前可见列表项的起始索引和数量,然后更新visibleStartIndex和visibleItemCount数据属性。最后,我们使用一些简单的样式来设置列表的高度和样式。
分析需求
首先,因为同事们对这个列表的长时间的使用已经习惯了,所以最好在体验上不要进行大的修改。为此,我需要把之前大概的功能列举出来:
- 每次列表有新的消息传入的时候,都要能看到最新的那条数据。
- 当用户点击列表中的其中一条数据的时候,列表需要停止更新,也就是停止滚动。
- 当列表处于锁定状态,滚动条滚动到最底部的时候,列表恢复自动滚动。
- 当列表中有被选中状态的数据时,可以通过上下左右键来让聚焦移动。
总结完之前的功能之后,我需要再梳理一下我的需求:
- 列表随着时间会越来越长,需要控制展示的节点数量。
- 列表长度随着WebSocket的通信而增加,数据更新频度过快,需要有缓冲池。
- 增加一个列表锁定的提示,可以手动解开列表的锁定。
- 移动聚焦的时候会随即展示日志详情,因为移动速度过快,所以需要增加防抖。
梳理完成之后,经过考虑我决定使用虚拟列表来代替现有的长列表,这也是踩坑之路的开始。
开始开发
长列表转虚拟列表
为什么需要虚拟列表
我们知道,在浏览器渲染页面的时候,当DOM节点的数量越多,每一次重绘的时候,对性能的影响也就越大。
假如我们需要展示一个信息量很大,大约有数十万条数据。遇到这样子的情况,其实现在有许多的方案,我们最常见的方案就类似PC上的下一页、上一页,但是这个方案在体验上其实并不友好。大部分的用户会比较喜欢不停的向下滚动就可以看到新的内容,但是这个就会遇到一个问题,不停的加载数据,导致页面堆积的节点越来越多,所消耗的内存不断增 大,最后连滚动都会卡顿。
这时候我们重新分析一下,就会发现其实有很多数据我们大多数情况下是不需要看见的,如果只考虑我们能看到数据的话,其实需要渲染的数据量就会非常的少了,很好的提高了渲染的效率,减少因为大量的重绘照成不必要的影响。
这么一梳理一下,答案简直呼之欲出—-虚拟列表。
什么是虚拟列表
虚拟列表其实没有什么特别神奇的地方,说白了就是一种展示列表的思路,在页面上创建一个容器作为可视区,在这个可视区内展示长列表中的一部分,也就是在可视区渲染列表。
如图中所示,是一个简单的虚拟列表的模型,图中有几个概念需要大家稍微了解一下:
1 |
|
通过这个样式我们可以看出这个可视区容器的高度为500px。
真实列表
真实列表就是会被渲染出来的列表,这么说可能不太理解,举个栗子:现在需要被渲染出来的列表数量一共有1000条,但是实际上在页面需要被渲染的列表数量(需要被看到的数据)只需要100条,这个100条就是所谓的真实列表。
1 |
|
在这里,建议真实列表的长度需要比可视区的高度长一些,有一个滚动条的话,之后可以通过scroll监听做一些其他的操作。
可能有一个点需要和大家解释一下为什么我的<div class="list-body">
是绝对定位。
当你的某一个元素会频繁发生变化的时候,最好将这个模块通过绝对定位的方式,脱离文档流,可以减少回流带来的影响。
我们先看一下浏览器的渲染机制
- 解析HTML,生成DOM树,解析CSS,生成CSSOM树
- 将DOM树和CSSOM树结合,生成渲染树(Render Tree)
- Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
- Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
- Display:将像素发送给GPU,展示在页面上。
绝对定位或者浮动脱离了正常的文档流,相当于只是在节点上存放了一个token,然后通过这个token去进行映射,所以如果你采用了绝对定位的方法,也只会对这一块元素进行重绘。
startIndex
之前也说到了,真实列表实际上只是总列表其中很小的一部分,在这之外还有很多列表需要被渲染。因此,大家可以把真实列表理解为一个片段。被渲染的第一个元素的index就是片段中第一个元素在总列表中的位置,也就是数组中的index。
举个栗子:我的总列表(数组)的长度为1000,而需要渲染的列表片段为100—200,那么这个开始的位置,也就是数组的index则为99。
edIndex
解释同上,最后一个元素的index是199。
虚拟列表的实现
这里要提一下,我的框架用的是vue,所以虚拟列表的实现也是比较方便的。
模板上,没有什么太特别的地方,主要就是通过v-if去控制列表的展示,通过startIdx和endIdx的增减,去展示不同位置的数据,让这两个值递增就可以实现列表滚动。
下边我们会说一下自动滚动在代码上的实现,主要是通过一个主动的事件去频繁的触发对startIdx和endIdx递增或者递减。
1 |
|
如上代码所示,我只需要再让一个方法去触发autoScroll(),这个方法就会在setTimeout的作用下自调用,startIdx和endIdx会不断递增列表就可以自动滚动了,在这里边有一个表达式
1 |
|
这一块的话主要是解决当页面刚打开或者清空列表的时候,实际上列表的长度比较短,是不需要进行滚动的,换句话说,startIndex需要在列表总长度在到达一个值之前一直为0。
到这里,简单的虚拟列表就实现了。
WebSocket缓冲池
我们使用的是WebSocket来传递数据,数据量不少。因此很可能会出现过于频繁更新数据的情况,数据一更新,页面也会随之改变,这样会对性能照成一定的影响。所以我们需要对这个频度进行把控。目前的方案是加一个缓冲池。
这缓冲池的思路大概是这样的,WebSocket传递数据的时候,我们把这段时间的数据先存在一个数组中,然后每隔一段时间,比如500ms,再把数据push到完整的列表中,这个方案可能就会涉及到节流。
1 |
|
在这里边 appendRecord() 是用来处理数据,并且把数据放入list中的方法,而 scrollToBottom() 就是为了当数据push到list之后,列表能直接展示最新的数据,也就是让页面滚动到列表的最底部。
缓冲池其实也是提升性能的一个方案,这个方案最核心的地方就是减少页面渲染的次数。大家可以这么理解:每秒钟可能会有10条数据需要被渲染,假如我每次都老老实实的渲染,那么10秒的时间我就要渲染10次,其实是没有必要的,因此我们可以考虑每2秒渲染一次,这样10s的时间内我的渲染次数就会减少到5次。你可以理解为性能提升了一倍。
列表锁定
按照之前的需求,当用户点击列表中的其中一条数据的时候,列表是需要停止滚动的。所以我加了一个滚动锁autoScrollLoack,这个锁的作用就是当我点击到列表中的某一条的时候,执行autoScrollLoack = true页面就不会滚动了。这个锁的判断会放在 this.scrollToBottom() 中,代码大家稍微看看就行。
1 |
|
这个autoScrollLoack在页面中会与一个单选框进行双向绑定,因此用户就可以通过改变单选框的选中状态来控制锁的状态,其实在有了这个锁之后,页面如果因为需求停止滚动了,用户也能有所感知,不至于突然滚动就停止了,看起来像个bug。
聚焦移动
聚焦移动的功能之前需求也说过了,就是选中了一条信息,可以通过上下键将聚焦指向上一个或者下一个,这个其实也比较好实现
1 |
|
在这里,大家可以看到,active就是聚焦的时候列表的样式。在逻辑上,把当前选中项的index赋给curIndex,前端模板上通过vue对class的绑定来控制样式,判断条件就是curIndex == index。
聚焦功能已经实现了,那么接下来要实现通过键盘中的上下键,实现移动聚焦的效果。这个功能很简单,我们完全可以通过vue提供的监听事件来实现,具体的实现大家可以在官网上搜一下keyup。
1 |
|
这段代码实现了聚焦的上下挪动。根据需求我们每一次聚焦的时候需要展示聚焦项对应的日志详情,详情是需要发ajax请求来获取的。问题来了,有一个场景:我想通过键盘把当前的聚焦向下挪动10次,在不停聚焦的过程中我会触发10次请求,这个其实没必要,我在快速移动的过程中,是不care详情的,我只需要展示目标详情就行了。综上,我们需要再加一个防抖。
1 |
|
从上边的逻辑我们可以看出来,当用户在快速挪动聚焦的时候是不会触发请求的,实际上这个改动很大程度上提升了用户的流畅度。
总结
计算可见列表项:首先,我需要计算当前可见的列表项数量和位置。这可以通过测量列表容器和列表项高度,以及滚动条的位置来实现。
动态渲染列表项:接下来,我需要根据当前可见的列表项数量和位置,动态地渲染这些列表项。这可以通过使用一个v-for指令来实现,只渲染当前可见的列表项。
优化性能:为了优化性能,我可以使用一些技巧,例如使用keep-alive缓存列表项,避免不必要的渲染,或使用Vue组件的生命周期钩子函数来处理列表项的渲染和销毁,以提高性能。
处理滚动事件:当用户滚动列表时,我需要根据滚动条的位置,重新计算当前可见的列表项,并相应地更新列表。这可以通过添加滚动事件监听器来实现。
处理列表项点击事件:最后,我需要处理列表项的点击事件。由于虚拟列表只渲染了当前可见的列表项,所以我需要确保只为当前可见的列表项添加事件处理程序,以避免不必要的性能开销。
总之,实现虚拟列表需要一定的计算和渲染技巧,需要注意性能优化和细节处理,可以借助第三方库来提高开发效率。当回答这个问题时,我需要清晰地表达我的思路和实现方案,注重细节和实现的可行性,并尽可能展示我的Vue编程技能和经验。
文章长列表优化这一块讲讲是怎么做的?
项目中文档分析模块显示的文档长列表会卡顿(如果数据量大的较大大约有数十万条数据,直接渲染所有数据会导致页面加载缓慢、卡顿等问题)–(performance查看可以看到执行栈task-帧执行(正常15ms,超过60ms))—-长列表是指包含大量数据项的列表,如果在渲染这些数据项时没有进行优化,则会导致应用程序变慢并占用更多的系统资源,从而影响用户体验。因此,需要对长列表进行优化。
2. 解决问题
优化的方法包括使用虚拟列表、懒加载、分页、无限滚动等技术。
原理
- 简要解释虚拟列表的概念和原理:虚拟列表是一种在渲染大型列表时非常有效的优化技术,它只会渲染可见的列表项,而不会渲染整个列表。它的原理是通过监听列表的滚动事件,在用户滚动列表时动态地渲染新出现的列表项,并销毁已经滚出视图的列表项,从而避免了长列表中大量无用数据的渲染和占用内存资源。
实现思路
讲解虚拟列表的优点:虚拟列表的主要优点包括:
优化性能:虚拟列表可以极大地减少在渲染长列表时的开销,从而提高应用程序的性能和响应速度。
节省内存:虚拟列表只渲染可见的列表项,而不会渲染整个列表,因此可以节省内存资源。
支持无限滚动:虚拟列表可以支持无限滚动,用户可以不断向下滚动列表,而不必等待整个列表渲染完成。
支持异步数据加载:虚拟列表可以与异步数据加载技术结合使用,当用户滚动到新的列表区域时,可以异步地加载数据并渲染新的列表项。
说明虚拟列表的缺点:虚拟列表的主要缺点是在实现时需要更多的代码复杂度和技术要求。特别是需要进行高级优化时,可能需要进行更多的计算和代码调试。此外,虚拟列表在处理动态高度的列表项时可能会存在一些问题,需要进行更多的测试和优化。
面试问你为什么这里使用虚拟列表而不使用懒加载、分页、无限滚动该怎么回答呢
虚拟列表可以提升用户体验:虚拟列表可以在页面上显示一定数量的列表项,而不会导致整个页面的渲染和布局都被阻塞。因此,当用户在列表中滚动时,新的列表项会被动态加载,而不会导致页面的重新渲染,从而提升了用户的体验。相比之下,懒加载、分页、无限滚动的加载方式可能会导致页面闪烁或整体布局变化,用户体验相对较差。
虚拟列表可以提高页面性能:由于虚拟列表只会渲染当前可视区域内的列表项,因此可以大大降低页面的渲染负担,提高页面的性能。相比之下,懒加载、分页、无限滚动的加载方式可能会导致不必要的DOM操作和数据绑定,从而影响页面性能。
虚拟列表适用于大数据量的列表展示:当需要在页面上展示大量数据时,虚拟列表是一种非常适用的方式。相比之下,懒加载、分页、无限滚动的加载方式通常只适用于数据量较小的列表展示,因为这些方式需要预先加载所有数据,并在用户滚动到相应位置时再进行渲染。
综上所述,虚拟列表相比于懒加载、分页、无限滚动具有更好的用户体验和页面性能,并且适用于大数据量的列表展示。因此,当需要在Web应用中展示大量数据时,使用虚拟列表是一种非常优秀的解决方案。
参考文章
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!